package fhffm.DagmAI.system;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import fhffm.DagmAI.operators.BitFlipMutation;
import fhffm.DagmAI.operators.OnePointCrossover;
import fhffm.DagmAI.operators.ParentSelection;
import fhffm.DagmAI.operators.SurvivorSelection;
import fhffm.DagmAI.util.GlobalRandom;

public class EvolutionSystem {
	private static Logger log = LogManager.getLogger(EvolutionSystem.class);
	private final double MUTATION_RATE;
	private final double CROSSOVER_RATE;
	private final int POPULATION_SIZE;

	public EvolutionSystem(double mutationRate, double crossoverRate, int populationSize) {
		this.MUTATION_RATE = mutationRate;
		this.CROSSOVER_RATE = crossoverRate;
		this.POPULATION_SIZE = populationSize;
	}

	/**
	 * TODO: Object -> Individual && Strategy Pattern?!
	 * Starts evolution and returns the final generation
	 * 
	 * @param verbose
	 *            generate verbose output
	 * @return the final generation
	 */
	public List<Object> startEvolution(boolean verbose) {
		// 1) Create initial Population
		List<Object> generation = createInitialPopulation(this.POPULATION_SIZE);
		if (verbose) {
			StringBuilder sb = new StringBuilder();
			sb.append("<generation");
			sb.append(" number=\"0\"");
			sb.append(" fitness=\"").append(getAverageFitness(generation)).append('\"');
			sb.append(" mutations=\"").append('0').append('\"');
			sb.append(" crossovers=\"").append('0').append('\"');
			sb.append(" />");
			log.debug(sb);
		}
		int generationNumber = 0;
		int mutationNumber = 0;
		// 2) Loop as long as termination condition is not reached
		while (!isTerminationConditionReached(generation)) {
			generationNumber++;
			// 3) Crossover n parents
			long crossoverCounter = Math.round((generation.size() * CROSSOVER_RATE) / 2);
			for (int k = 0; k < crossoverCounter; k++) {
				List<Object> parents = ParentSelection.selectParents(generation);
				List<Object> children = OnePointCrossover.crossover(parents.get(0), parents.get(1));
				// 4) Mutate children with certain probability
				for (Object child : children) {
					if (GlobalRandom.random.nextFloat() >= MUTATION_RATE) {
						mutationNumber++;
						generation.add(BitFlipMutation.mutate(child));
					} else {
						generation.add(BitFlipMutation.mutate(child));
					}
				}
			}
			
			// 5) Survivor Selection
			generation = SurvivorSelection.selectSurvivors(generation);

			if (verbose) {
				StringBuilder sb = new StringBuilder();
				sb.append("<generation");
				sb.append(" number=\"").append(generationNumber).append("\"");
				sb.append(" fitness=\"").append(getAverageFitness(generation)).append('\"');
				sb.append(" mutations=\"").append(mutationNumber).append('\"');
				sb.append(" crossovers=\"").append(crossoverCounter).append('\"');
				sb.append(" />");
				log.debug(sb);
			}
		}
		return generation;
	}

	// TODO: Object -> Individual
	private List<Object> createInitialPopulation(int number) {
		// TODO: Object -> Individual
		List<Object> generation = new LinkedList<>();
		for (int i = 0; i < number; i++) {
			generation.add(new Object());
		}
		// return generation;
		throw new UnsupportedOperationException("Not yet implemented");
	}

	// TODO: Object -> Individual
	private boolean isTerminationConditionReached(List<Object> generation) {
		throw new UnsupportedOperationException("Not yet implemented");
	}

	// TODO: Object -> Individual
	private double getAverageFitness(List<Object> generation) {
		throw new UnsupportedOperationException("Not yet implemented");
	}

}
